package dockermonitor

import (
	"fmt"
	corev1 "gluenet/internal/agent/api/core/v1"
	rpcv1 "gluenet/internal/agent/api/rpc/v1"
	"gluenet/internal/agent/cache/object"
	"gluenet/internal/agent/depend"
	"gluenet/internal/agent/driver/hostdriver"
	"gluenet/internal/agent/monitor/option"
	"os/exec"
	"path/filepath"

	"github.com/spf13/viper"
	"golang.org/x/sync/errgroup"
)

func init() {
	depend.Provide(NewDockerMonitorBooter)
}

type DockerMonitorBooter struct {
	components []string
	configRoot string
	objects    object.Interface
}

func NewDockerMonitorBooter(objects object.Interface) *DockerMonitorBooter {
	return &DockerMonitorBooter{
		objects: objects,
	}
}

func (r *DockerMonitorBooter) Init(opts *option.MonitorOptions) (err error) {
	r.configRoot = option.GenMointorConfigPath(`docker`)
	r.components = opts.Components

	var eg errgroup.Group
	for _, component := range opts.Components {

		switch component {
		case `prometheus`:
			eg.Go(func() error { return r.initPrometheus(opts.Guid, hostdriver.GetHost().HostName) })
		case `vector`:
			eg.Go(func() error { return r.initVector(opts.Guid, opts.Nats) })
		case `jaeger`:
			eg.Go(func() error { return r.initJaeger(opts.Guid, opts.Nats) })
		}

	}

	return eg.Wait()
}

func (r *DockerMonitorBooter) initPrometheus(guid, hostname string) error {
	initScript := filepath.Join(r.configRoot, `metric/prometheus/set_up.sh`)

	cmd := exec.Command(initScript, guid, hostname)
	fmt.Println(cmd)
	if err := cmd.Run(); err != nil {
		return err
	}

	return r.runSetScript(`metric/prometheus`, guid)
}

const (
	keySourceDockerInlcudeContainers = "sources.docker.include_containers"
)

func (r *DockerMonitorBooter) initVector(guid, rpcConfig string) error {
	configPath := filepath.Join(r.configRoot, `log/vector/vector/vector.toml`)

	r.objects.OnPut(rpcv1.CONTAINER_RESOURCE, func(guid string, value interface{}) {
		containerObj, ok := value.(*corev1.DockerContainerObj)
		if !ok {
			return
		}

		vp := viper.New()
		vp.SetConfigFile(configPath)
		vp.ReadInConfig()

		includeContainers := vp.GetStringSlice(keySourceDockerInlcudeContainers)
		includeContainers = append(includeContainers, containerObj.Spec.ID)
		vp.Set(keySourceDockerInlcudeContainers, includeContainers)
		vp.WriteConfig()
	})

	r.objects.OnDelete(rpcv1.CONTAINER_RESOURCE, func(guid string, value interface{}) {
		containerObj, ok := value.(*corev1.DockerContainerObj)
		if !ok {
			return
		}

		vp := viper.New()
		vp.SetConfigFile(configPath)
		if err := vp.ReadInConfig(); err != nil {
			return
		}

		includeContainers := vp.GetStringSlice(keySourceDockerInlcudeContainers)

		var newContainers []string
		if len(includeContainers) > 1 {
			newContainers = make([]string, len(includeContainers)-1)
			i := 0
			for _, ct := range includeContainers {
				if ct == containerObj.Spec.ID {
					continue
				}
				newContainers[i] = ct
				i++
			}
		}

		vp.Set(keySourceDockerInlcudeContainers, newContainers)
		if err := vp.WriteConfig(); err != nil {
			return
		}

	})
	return r.runSetScript(`log/vector`, guid, rpcConfig)
}
func (r *DockerMonitorBooter) initJaeger(guid, rpcConfig string) error {

	return r.runSetScript(`trace/jaeger`, guid, rpcConfig)
}

func (r *DockerMonitorBooter) clearPrometheus() error {
	return r.runClearScript(`metric/prometheus`)
}

func (r *DockerMonitorBooter) clearVector() error {
	return r.runClearScript(`log/vector`)
}
func (r *DockerMonitorBooter) clearJaeger() error {
	return r.runClearScript(`trace/jaeger`)
}

func (r *DockerMonitorBooter) runSetScript(args ...string) error {
	script := filepath.Join(r.configRoot, `set_or_clear.sh`)

	allArgs := append([]string{`set`}, args...)
	cmd := exec.Command(script, allArgs...)
	fmt.Println(cmd)
	if err := cmd.Run(); err != nil {
		return err
	}

	return nil
}

func (r *DockerMonitorBooter) runClearScript(target string) error {
	script := filepath.Join(r.configRoot, `set_or_clear.sh`)

	cmd := exec.Command(script, `clear`, target)
	fmt.Println(cmd)
	if err := cmd.Run(); err != nil {
		return err
	}

	return nil
}

func (r *DockerMonitorBooter) Close() (err error) {
	var eg errgroup.Group
	for _, component := range r.components {

		switch component {
		case `prometheus`:
			eg.Go(func() error { return r.clearPrometheus() })
		case `vector`:
			eg.Go(func() error { return r.clearVector() })
		case `jaeger`:
			eg.Go(func() error { return r.clearJaeger() })
		}

	}
	return eg.Wait()
}
